package com.zeyu.framework.core.web.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 主要负责系统异常页面的处理和错误信息的展示
 * <p>
 * 在执行@RequestMapping之前遇到的异常,该类异常通常是因为SpringMVC没有找到能处理当前请求的控制器造成的，
 * 比如最常见的404。此时@ControllerAdvice就不起作用了，因为@RequestMapping方法根本没找到，更不可能执行。
 * 此时我们要写一个@RestController，并实现ErrorController接口
 * Created by zeyuphoenix on 16/6/28.
 */
@Controller
@RequestMapping(value = "/error")
@EnableConfigurationProperties({ServerProperties.class})
public class FinalExceptionController implements ErrorController {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(FinalExceptionController.class);

    // ================================================================
    // Fields
    // ================================================================

    /**
     * 异常参数封装
     */
    private ErrorAttributes errorAttributes;

    @Autowired
    private ServerProperties serverProperties;

    // ================================================================
    // Constructors
    // ================================================================

    /**
     * 初始化
     */
    @Autowired
    public FinalExceptionController(ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    @Override
    public String getErrorPath() {
        return "/error";
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * 默认异常处理
     */
    @RequestMapping(value = "")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        // 错误处理逻辑
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);

        logger.error("系统未知异常: {}", body);
        return new ResponseEntity<>(body, status);
    }

    /**
     * 默认异常处理
     */
    @RequestMapping(value = "", produces = "text/html")
    public String errorHtml(HttpServletRequest request) {
        // 错误处理逻辑
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);

        if (status.is5xxServerError()) {
            return "redirect:/error/500";
        } else if (status.value() == 400) {
            return "redirect:/error/400";
        } else if (status.value() == 403) {
            return "redirect:/error/403";
        } else if (status.value() == 404) {
            return "redirect:/error/404";
        }

        logger.error("系统未知异常: {}", body);
        return "redirect:/error/500";
    }

    /**
     * 定义401的JSON数据
     */
    @RequestMapping(value = "400")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error400(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);
        logger.error("400 error: {}", body);
        return new ResponseEntity<>(body, status);
    }

    /**
     * 定义404的JSON数据
     */
    @RequestMapping(value = "400", produces = "text/html")
    public String error400Html(HttpServletRequest request, Model model) {
        HttpStatus status = getStatus(request);

        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        logger.error("400 error: {}", body);
        model.addAttribute("message", "页面请求格式错误");
        model.addAttribute("error", body);
        model.addAttribute("status", status);
        return "error/400";
    }

    /**
     * 定义403的JSON数据
     */
    @RequestMapping(value = "403")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error403(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);
        logger.error("403 error: {}", body);
        return new ResponseEntity<>(body, status);
    }

    /**
     * 定义404的JSON数据
     */
    @RequestMapping(value = "403", produces = "text/html")
    public String error403Html(HttpServletRequest request, Model model) {
        HttpStatus status = getStatus(request);

        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        logger.error("403 error: {}", body);
        model.addAttribute("message", "页面未授权");
        model.addAttribute("error", body);
        model.addAttribute("status", status);
        return "error/403";
    }


    /**
     * 定义404的JSON数据
     */
    @RequestMapping(value = "404")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error404(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);
        logger.error("404 error: {}", body);

        return new ResponseEntity<>(body, status);
    }

    /**
     * 定义404的JSON数据
     */
    @RequestMapping(value = "404", produces = "text/html")
    public String error404Html(HttpServletRequest request, Model model) {
        HttpStatus status = getStatus(request);

        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        logger.error("404 error: {}", body);
        model.addAttribute("message", "页面未找到");
        model.addAttribute("error", body);
        model.addAttribute("status", status);
        return "error/404";
    }

    /**
     * 定义500的错误JSON信息
     */
    @RequestMapping(value = "500")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error500(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);
        body.put("error", "程序处理异常");
        logger.error("500 error: {}", body);
        return new ResponseEntity<>(body, status);
    }

    /**
     * 定义500的错误HTML
     */
    @RequestMapping(value = "500", produces = "text/html")
    public String error500Html(HttpServletRequest request, Model model) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        logger.error("500 error: {}", body);
        HttpStatus status = getStatus(request);
        model.addAttribute("message", "程序处理异常");
        model.addAttribute("error", body);
        model.addAttribute("status", status);
        return "error/500";
    }


    /**
     * Determine if the stacktrace attribute should be included.
     *
     * @param request  the source request
     * @param produces the media type produced (or {@code MediaType.ALL})
     * @return if the stacktrace attribute should be included
     */
    protected boolean isIncludeStackTrace(HttpServletRequest request,
                                          MediaType produces) {
        ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        logger.debug("request media type is {} ", produces);
        return include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM && getTraceParameter(request);
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    /**
     * 获取错误的信息
     */
    private Map<String, Object> getErrorAttributes(HttpServletRequest request,
                                                   boolean includeStackTrace) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        return this.errorAttributes.getErrorAttributes(requestAttributes,
                includeStackTrace);
    }

    /**
     * 是否包含trace
     */
    private boolean getTraceParameter(HttpServletRequest request) {
        String parameter = request.getParameter("trace");
        return parameter != null && !"false".equals(parameter.toLowerCase());
    }

    /**
     * 获取错误编码
     */
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request
                .getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        try {
            return HttpStatus.valueOf(statusCode);
        } catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
